package hello.word.m2.maven.support;

import hello.word.m2.maven.jpa.Jpqls;
import hello.word.m2.maven.jpa.SQLDialect;
//import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.Query;

import javax.annotation.Nullable;
import javax.persistence.Table;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.lang.String.format;

public interface CrudDao<ENTITY extends Entity<ID>, ID> extends InternalCrudDao<ENTITY, ID> {

	default List<ENTITY> findListByColumnValue(/*@NonNull*/ String column, @Nullable Object value) {
		String operand = value == null ? "is" : "=";
		return internalFind(getTableName(), column, value, operand, getIdColumnName());
	}

	default List<ENTITY> findListByColumnValue(/*@NonNull*/ DaoConditionChain conditionChain) {
		final String cString = conditionChain.asString();
		if (StringUtils.isBlank(cString)) {
			return internalFindAll(getTableName(), getIdColumnName(), OrderType.ASC, 100);
		} else {
			return internalFindConditions(getTableName(), cString, getIdColumnName());
		}
	}

	default Optional<ENTITY> findOneByColumnValue(/*@NonNull*/ String column, @Nullable Object value) {
		return findListByColumnValue(column, value).stream().findFirst();
	}

	default Optional<ENTITY> findOneByColumnValue(/*@NonNull*/ DaoConditionChain conditionChain) {
		return findListByColumnValue(conditionChain).stream().findFirst();
	}

	default List<ENTITY> findLast(int limit) {
		return internalFindAll(getTableName(), getIdColumnName(), OrderType.DESC, limit);
	}

	default List<ENTITY> findFirst(int limit) {
		return internalFindAll(getTableName(), getIdColumnName(), OrderType.ASC, limit);
	}

	default Optional<ENTITY> findOne(/*@NonNull*/ ID id) {
		return findOneByColumnValue(getIdColumnName(), id);
	}

	default int updateByColumnValue(/*@NonNull*/ String column, @Nullable Object value,
                                    /*@NonNull*/ DaoConditionChain conditionChain) {

		final String cString = conditionChain.asString();
		if (StringUtils.isBlank(cString)) {
			return internalUpdateAll(getTableName(), column, value);
		} else {
			return internalUpdateConditions(getTableName(), column, value, cString);
		}
	}

	default ID insert(/*@NonNull*/ ENTITY entity) {
		Entity.KeyValueMap all = entity.allFields();
//        final ID id = entity.id();
//		internalInsert(getTableName(), all.getKeysAsList(), all.getValuesAsList());
		return internalInsertReturnID(getTableName(), all.getKeysAsList(), all.getValuesAsList());
	}

	default ID update(/*@NonNull*/ ENTITY entity) {
		final ID id = entity.id();
		if (id == null) {
			throw new DaoException(
					format("[%s] Не указан id записи для обновления", entity.getClass().getSimpleName()));
		}
		final Optional<ENTITY> existsEntity = findOne(id);
		if (!existsEntity.isPresent()) {
			throw new DaoException(format(
					"[%s] Запись с id = '%s' не существует. Создайте ее или воспользуйтесь методом updateOrInsert",
					entity.getClass().getSimpleName(), id));
		}
		final Entity.KeyValueMap toUpdate = entity.allFields().minus(existsEntity.get().allFields(), true);
		toUpdate.getSourceMap().forEach((k, v) -> internalUpdate(getTableName(), k, v, getIdColumnName(), id));
		return id;
	}

	default int delete(/*@NonNull*/ ID id) {
		return internalDelete(getTableName(), getIdColumnName(), id);
	}

	default void truncate() {
		internalTruncate(getTableName());
	}

	default long count() {
		return internalCount(getTableName());
	}

	/*@NonNull*/
	default String getTableName() {
		// TODO tableName
		ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getInterfaces()[0]
				.getGenericInterfaces()[0];
		Type type = parameterizedType.getActualTypeArguments()[0];
		String typeName = type.getTypeName();
		Class<?> clazz=(Class<?>)type;
		Table table=clazz.getAnnotation(Table.class);
		String tableName = typeName.substring(typeName.lastIndexOf(".") + 1, typeName.length());
		if(null != table){
			tableName= table.name();
		}
		return tableName.toLowerCase();
	}

	/*@NonNull*/
	default String getIdColumnName() {
		return "id";
	}

	default <T> PageDto<T> getPage(String sql, Map<String, Object> param, PageDto<T> page, RowMapper<T> mapper){
		//TODO only support mysql now
		int begin = page.getCurrent()*page.getSize();
		int end = begin + page.getSize();
		StringBuilder sb = new StringBuilder();
		sb.append(sql);

		try(Handle handle = getHandle()) {
			Jpqls config=handle.getConfig().get(Jpqls.class);
			SQLDialect dialect = config.getJpqlBuilder().getSQLDialect();
			//mysql
			if(SQLDialect.MYSQL.equals(dialect)){
				sb.append("  limit ").append(begin).append(", ").append(end);
			}
			Query query=handle.createQuery(sb.toString());
			for (String key : param.keySet()){
				query.bind(key,param.get(key));

			}
			List<T> result=query.map(mapper)
					.list();
			page.setContent(result);
		}
//		Handle handle=getHandle();
		return page;
	}
}